package com.liujit.standard.basis.admin.config.aop;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.liujit.standard.basis.admin.config.event.SysLogEvent;
import com.liujit.standard.basis.admin.model.SysLog;
import com.liujit.standard.basis.admin.util.ApiUtil;
import com.liujit.standard.basis.core.domain.result.UserInfo;
import com.liujit.standard.basis.core.util.IpUtil;
import com.liujit.standard.basis.core.util.JsonUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;

/**
 * @description: 异步日志记录
 * @author: liujun
 * @create: 2021/3/24 6:38 下午
 **/
@Configuration
@Aspect
@Order(1)
@Slf4j
public class SysLogAop {

    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    /**
     * 事件发布是由ApplicationContext对象管控的
     * 通过方法publishEvent 发布事件
     **/
    @Resource
    private ApplicationContext applicationContext;


    /***
     * 定义controller切入点拦截规则，拦截SysLog注解的方法
     */
    @Pointcut("@annotation(com.liujit.standard.basis.admin.config.annotation.Log)")
    public void sysLogAspect() {

    }


    @Before(value = "sysLogAspect()")
    public void logMethod(JoinPoint joinPoint) throws Throwable {
        SysLog sysLog = new SysLog();
        //将当前实体保存到threadLocal
        sysLogThreadLocal.set(sysLog);

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        UserInfo user = ApiUtil.getSysUser();
        sysLog.setUserId(user.getId());
        sysLog.setUserName(user.getLoginName());
        sysLog.setActionUrl(URLUtil.getPath(request.getRequestURI()));
        String ip = ServletUtil.getClientIP(request);
        sysLog.setIp(ip);
        sysLog.setLocation(IpUtil.getCityInfo(ip));
        sysLog.setRequestMethod(request.getMethod());
        String uaStr = request.getHeader("user-agent");
        sysLog.setBrowser(UserAgentUtil.parse(uaStr).getBrowser().toString());
        sysLog.setOs(UserAgentUtil.parse(uaStr).getOs().toString());

        //访问目标方法的参数 可动态改变参数值
        Object[] args = joinPoint.getArgs();
        sysLog.setClassPath(joinPoint.getTarget().getClass().getName());
        // 参数
        sysLog.setParams(Arrays.toString(args));
        sysLog.setDescription(getAnnotationDescription(joinPoint));
    }

    @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
    public void doAfterReturning(Object ret) {
        //得到当前线程的log对象
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setResultCode(Convert.toShort(1));
        sysLog.setType(Convert.toShort(1));
        sysLog.setResult(JSON.toJSONString(ret));
        // 处理完请求，返回内容
        if (ret instanceof Boolean) {
            Boolean res = (Boolean) ret;
            sysLog.setResult(Convert.toStr(res));
        } else {
            sysLog.setExDetail(JsonUtil.toJsonString(ret));
        }
        // 发布事件
        applicationContext.publishEvent(new SysLogEvent(sysLog));
        //移除当前log实体
        sysLogThreadLocal.remove();
    }

    @AfterThrowing(throwing = "ex", pointcut = "sysLogAspect()")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {//注解标注的方法抛出的异常
        //得到当前线程的log对象
        SysLog sysLog = sysLogThreadLocal.get();

        sysLog.setType(Convert.toShort(2));
        sysLog.setResultCode(Convert.toShort(0));
        sysLog.setExDetail(JSON.toJSONString(ex));
        sysLog.setExMsg(ex.getMessage());
        // 发布事件
        applicationContext.publishEvent(new SysLogEvent(sysLog));
        //移除当前log实体
        sysLogThreadLocal.remove();
    }

    private String getAnnotationDescription(JoinPoint point) throws Exception {
        // 获取连接点目标类名
        String targetName = point.getTarget().getClass().getName();
        // 获取连接点签名的方法名
        String methodName = point.getSignature().getName();
        //获取连接点参数
        Object[] args = point.getArgs();
        //根据连接点类的名字获取指定类
        Class targetClass = Class.forName(targetName);
        //获取类里面的方法
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == args.length) {
                    description = method.getAnnotation(ApiOperation.class).value();
                    break;
                }
            }
        }
        return description;
    }
}
